""" 

    Python logging compatible loggers for Pys60.
    
"""

__author__ = "Mikko Ohtamaa <mikko@redinnovation.com>"
__docformat__ = "epytext"
__copyright__ = "Red Innovation Ltd."
__license__ = "BSD"

import os
import sys
import time
import e32

# Copy logging/* files from Python 2.5 distribution
import logging
from logging import handlers
from logging.handlers import BufferingHandler, MemoryHandler

class S60Handler(MemoryHandler):
    """ Use e32 synchronization to provide Symbian multithread aware logger wrapper.
    
    In Symbian, threads cannot share file descriptors, and thus console.
    This hanlder buffers data in-memory and periodically signals the application
    main thread to flush the output.
    
    Output from UI thread appears instantly. Other threads must wait
    until main thread calls S60Logging.execute_main_thread_operations().
    
    Notes: self.lock didn't work. self.lock become None after __init__
    in other methods. Is it a reserved keyword or something?
    """
    
    def __init__(self, signaller, target, capacity):
        """
        
        @param capacity Number of log messages buffer before main thread flush
        @param target Target logging handler which is executed in the main thread
        @param lock Ao_lock() object of the main UI thread
        """
        #print "Init, lock:" + str(lock)
        assert signaller != None
        self.signaller = signaller
        
        MemoryHandler.__init__(self, capacity=capacity, target=target)
        
        # Buffer of log messages which have been already transferred to UI thread
        self.mainThreadBuffer = [] 
        
    def setFormatter(self, formatter):
        """ Delegate format to the actual logger """
        self.target.setFormatter(formatter)
        
        
    def flush(self):
        """ Capacity is full, trigger main thread to flush the log buffer.
        
        Since capacity = 0, this should happen after the every log message
        and no log messages are lost.
        """
        
        # Copy messages to UI thread buffer
        # TODO: Make operation atomic???
        for val in self.buffer:
            self.mainThreadBuffer.append(val)

        # All messages have been given for the main thread
        self.buffer = []
        
        # Notify main thread that we ahve buffered stuff for it
        self.signaller()
        
    def flushInMainThread(self):
        """ Call this method co-operatively in the app main thread.
        
        This delegates the current log buffer to the target log handler
        (file or console) and it's executed in the context of main thread.
        """    
        for record in self.mainThreadBuffer:
            if self.target != None:
                self.target.handle(record)
        self.mainThreadBuffer = []
        
    def emit(self, record):
        """ """
        # call parent
        MemoryHandler.emit(self, record)
        
        # If we are the ui thread, flush instantly
        if e32.is_ui_thread():
            self.flush()
            self.flushInMainThread()
        
class S60FileHandler(S60Handler):
    """ Multithread aware S60 file logger. """
    
    def __init__(self, lock, file, capacity=0):
        target = logging.FileHandler(file)
        S60Handler.__init__(self, lock, target=target, capacity=capacity)
        
        
    
class S60ConsoleHandler(S60Handler):
    """ Output log to Python interpreter console on S60.
    
    Alternatively, log goes to putools console on PC over Bluetooth.
    """

    def __init__(self, lock, capacity=0):
        target = logging.StreamHandler(sys.stdout)
        S60Handler.__init__(self, lock, target=target, capacity=capacity)
        
        

class S60LoggingManager:
    """ Manage different loggers on Series 60.
    
    
    Example::
    
        from lifematta.s60utils.s60logging import S60LoggingManager
        
        
    
    """

    ## Static list of loggers which should be flushed in the main thread
    main_thread_loggers = []
    
    ## Where per-thread log files are stored (static)
    log_file_folder = "E:\\"
    
    ## Logger which logs output to the Python console (stdout) and app specific file
    basic_logger = None
    
    def execute_main_thread_operations():
        """ For all loggers, do required single thread operations """
        for logger in S60LoggingManager.main_thread_loggers:
            for handler in logger.handlers:
                if hasattr(handler, "flushInMainThread"):
                    # our logging handler baby
                    handler.flushInMainThread()
            
    execute_main_thread_operations = staticmethod(execute_main_thread_operations)
    
    def register_main_thread_logger(logger):
        S60LoggingManager.main_thread_loggers.append(logger)
    register_main_thread_logger = staticmethod(register_main_thread_logger)
    
    def create_per_thread_logger(name):        
        """ Creating Python logging object for this thread. 
        
        This logger is not available outside this thread. Calling
        it outside will crash the application. Since this logger
        does not depend on other threads, the log is written even 
        the main thread of the application stops responding.
        
        @param name Thread name, arbitary string
        """
        logger = logging.getLogger(name)
       
        fname = os.path.join(S60LoggingManager.log_file_folder, name + ".log.txt")
        
        formatter = logging.Formatter('%(asctime)s %(levelname)5s %(message)s')
    
        # Log file handler
        handler = logging.FileHandler(fname)
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        
        logger.setLevel(logging.DEBUG)
            
        return logger
    create_per_thread_logger = staticmethod(create_per_thread_logger)
    
    def get_basic_logger():
        return S60LoggingManager.basic_logger
    get_basic_logger = staticmethod(get_basic_logger)
    
    def setup_basic_logging(lock, fname, debug=False, format='%(asctime)s %(levelname)5s %(message)s'):
        """ Create multithread aware logger which outputs to stdout and file.
        
        You must call S60loggingManager.execute_main_thread_operations()
        in your main thread periodically.
        
        @param lock Application main thread e32.Ao_lock object
        @param format Logging format string
        @param appname String, your application name.
        """

        
        
        # Setup logging API
        
        # Just call this to set-up custom logging level in your app
        # Just call this to set-up custom logging level in your app
        if debug:
            logging.root.setLevel(logging.DEBUG)
        else:
            logging.root.setLevel(logging.INFO)
        
        # We need to remove default StreamHandler(sys.stdout)
        # since it will crash the application when called in the other 
        # threads
        logging.root.handlers = []
        
        logger = logging.getLogger(fname)
                
        formatter = logging.Formatter(format)
        
        # Create file logger
        handler = S60FileHandler(lock, fname)
        handler.setFormatter(formatter)    
        logging.root.addHandler(handler)
        
        # Create stdout logger
        handler = S60ConsoleHandler(lock)
        handler.setFormatter(formatter)
        logging.root.addHandler(handler)    
        
        S60LoggingManager.register_main_thread_logger(logger)
        S60LoggingManager.basic_logger = logger
        
        # Wrap accidental stdout calls
        
        # E.g. socket.py has them 
        
        class StdoutWrapper:
         
            def write(self, *args, **kwargs):
                self.logger.debug("STDOUT CATCHED:%s %s" % (str(args), str(kwargs)))
                
            def flush(self):
                 pass
                
        #sys.old_stdout = sys.stdout
        #sys.stdout = StdoutWrapper()
        #sys.stdout.logger = logger
        
        return logger
    setup_basic_logging = staticmethod(setup_basic_logging)
    
    
    def cleanup():
        """ Close all loggers and release file handlers.        
        """
        logging.shutdown()
        
    cleanup = staticmethod(cleanup)
    
def test():    
    from timer import Signaller
    global logger
    global main_thread_timer
    from lifematta.s60utils.timer import SafeLock
        
    app_lock = SafeLock()
    print "Created lock:" + str(app_lock)
            
    logger = S60LoggingManager.setup_basic_logging(Signaller(app_lock), "Logging test")
    logger.debug("Hello world")
    
    def thread_two():
        try:
            import time
            global logger
            death_point = time.time() + 14
            while time.time() < death_point:
                logger.debug("Thread 2 output")
                e32.ao_sleep(1)
        except:
            pass
            
        
    thread.start_new_thread(thread_two, ())
    
    # Run test 5 seconds
    death_point = time.time() + 20
    
    while time.time() < death_point:
        app_lock.reset()
        print "Running main loop, this is normal stdout output"
        logger.debug("This is logger output")
        
        # Flush all loggers shared between threads.
        # No output is available from non-UI threads until this method is called.
        S60LoggingManager.execute_main_thread_operations()        
        app_lock.wait(1)
        
 
if __name__ == '__main__':
    # This file is executed as the app start point
    # Execute very simple self unit test.
    test()
    